// midiparser.cpp

// 2008-04-20 22:55:12 (Jake): created

#include "crankit_precompiled.h"

#include <string>
#include <map>
#include <stdexcept>
#include <sstream>
#include <fstream>
#include <iostream>

#include "midiparser.h"

using namespace std;

Buffer::Buffer() : _buf(0), _size(0), _pos(0)
{

}

Buffer::~Buffer()
{
	destroy();
}

void Buffer::destroy()
{
	delete _buf;
	_buf = 0;
	_size = 0;
	_pos = 0;
}

void Buffer::read(const std::string& filename)
{
	destroy();

	std::ifstream fs;
	fs.open(filename.c_str());

	if (!fs) {
		throw runtime_error("failed to open file for reading: " + filename);
	}

	fs.seekg(0, std::ios::end);
	std::streamsize size = fs.tellg();
	fs.seekg(0, std::ios::beg);

	if (size > 0) {
		_buf = new unsigned char[size];
		fs.read((char*)_buf, size);
	}

	_size = size;
	_pos = 0;
}

int MidiParser::readHeader(const std::string& filename, MidiOutput& output)
{
	_buffer.read(filename);

	MidiChunkHeader midiHeader;
	read(midiHeader);

	if (midiHeader.id != "MThd") {
		throw std::runtime_error("Midi file does not contain a correct header");
	}

	int type = readShort();
	int numTracks = readShort();

	unsigned char div_b1 = _buffer.read();
	unsigned char div_b0 = _buffer.read();

	output.header(type, numTracks, div_b1, div_b0);

	return numTracks;
}

void MidiParser::readHeaderAndTracks(const std::string& filename, MidiOutput& output)
{
	int numTracks = readHeader(filename, output);

	for (int t = 0; t < numTracks; t++) {
		readTrack(t, output);
	}
}

void MidiParser::readTrack(int trackNum, MidiOutput& output)
{
	const unsigned char PatchChange = 0xc0;
	const unsigned char ChannelPreasure = 0xd0;
	const unsigned char NoteOn = 0x90;
	const unsigned char NoteOff = 0x80;
	const unsigned char AfterTouch = 0xa0;
	const unsigned char ContinousController = 0xb0;
	const unsigned char PitchBend = 0xe0;

	struct MidiEvent {
		std::string name;
		int length;

		MidiEvent() { }
		MidiEvent(const std::string& name, int length) : name(name), length(length) { }
	};

	typedef std::map<unsigned char, MidiEvent> MidiEventMap;
	MidiEventMap eventMap;
	eventMap[PatchChange] = MidiEvent("PatchChange", 1);
	eventMap[ChannelPreasure] = MidiEvent("ChannelPreasure", 1);
	eventMap[NoteOn] = MidiEvent("NoteOn", 2);
	eventMap[NoteOff] = MidiEvent("NoteOff", 2);
	eventMap[AfterTouch] = MidiEvent("AfterTouch", 2);
	eventMap[ContinousController] = MidiEvent("ContinousController", 2);
	eventMap[PitchBend] = MidiEvent("PitchBend", 2);

	MidiChunkHeader trackHeader;
	read(trackHeader);
	if (trackHeader.id != "MTrk") {
		std::ostringstream os;
		os << "corrupt midi file. expected MTrk, got: '" << trackHeader.id << "'";
		throw std::runtime_error(os.str());
	}

	unsigned int trackSize = trackHeader.length;
	unsigned int trackEnd = _buffer.getPos() + trackSize;

	output.beginTrack(trackNum, trackSize);

	_time = 0;
	_status = 0;
	int status = 0;

	const int MetaEvent = 0xff;
	const int MetaEventEndOfTrack = 0x2f;
	const int MetaEventTempo = 0x51;

	while (_buffer.getPos() < trackEnd) {
		unsigned int relTime = readVarLen();

		output.relTime(relTime);

		_time += relTime;

		unsigned char peek = _buffer.peek();
		if ((peek & 0x80) != 0) {
			status = _buffer.read();
			if (peek != 0xff && peek != 0xf0) {
				_status = status;
			}
		}
		else {
			status = _status;
		}

		if (status == MetaEvent) {
			unsigned char metaType = _buffer.read();
			unsigned int metaLength = readVarLen();
			unsigned char* metaData = _buffer.read(metaLength);

			//output.metaEvent(metaType, metaData, metaLength);

			if (metaType == MetaEventEndOfTrack) {
				output.endTrack();
				return; 
			}
			else if (metaType == MetaEventTempo) {
				int tempo = (metaData[0] << 16) | (metaData[1] << 8) | metaData[2];
				output.tempo(tempo);
			}
		}
		else if (status == 0xf0) {
			unsigned int length = readVarLen();
			unsigned char* data = _buffer.read(length-1);

			unsigned char peek = _buffer.peek();
			if (peek == 0xf7) {
				_buffer.read();
			}
		}
		else if ((status & 0xf0) == 0xf0) {
			int type = status & 0x0f;
			int length = 0;
			if (status == 0xf1) {
				length = 1;
			}
			else if (status == 0xf2) {
				length = 2;
			}
			else if (status == 0xf3) {
				length = 1;
			}

			unsigned char* data = _buffer.read(length);
		}
		else {
			MidiEventMap::const_iterator iter = eventMap.find(status & 0xf0);
			
			if (iter == eventMap.end()) {
				std::ostringstream os;
				os << "unknown midi event: " << (int)(status & 0xf0);
				throw std::runtime_error(os.str());
			}

			const MidiEvent& e = iter->second;

			int type = status & 0xf0;
			int channel = status & 0x0f;
			int length = e.length;
			unsigned char* data = _buffer.read(length);

			switch (type) {
				case NoteOn: {
					output.eventNoteOn(channel, data[0], data[1]);
					break;
				}
				case NoteOff: {
					output.eventNoteOff(channel, data[0], data[1]);
					break;
				}
				default: {
					break;
				}
			}
		}
	}
}

void MidiParser::read(MidiChunkHeader& header)
{
	unsigned char* id = _buffer.read(4);
	unsigned int length = readLong();

	header.id = std::string((char*)id, 4);
	header.length = length;
}

unsigned int MidiParser::readVarLen()
{
	unsigned char b = _buffer.read();
	unsigned int value = b;

	if ((b & 0x80) != 0) {
		value &= 0x7f;
		do {
			b = _buffer.read();
			value = (value << 7) | (b & 0x7f);
		} while ((b & 0x80) != 0);
	}

	return value;
}

#if 0
int main(int argc, char** argv)
{
	MidiParser parser;

	try {
		parser.read("C:/Private/Games/FretsOnFire/data/songs/bangbang/notes.mid");
	}
	catch (std::runtime_error& e) {
		std::cerr << "error: " << e.what() << std::endl;
	}

	return 0;
}
#endif 